package main;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import main.BackPropagation.Data;

public class IrisNetwork extends NeuralNetwork{

	InputNeuron[] inputs;
	Neuron[] outputs;

	public static double WRONGVAL = 1;
	public static double RIGHTVAL = 0;

	public static int numOutputs = 3;


	public IrisNetwork(int layers, int N) {
		super(new Vector<InputNeuron>(), new Vector<Vector<Neuron> >(), new Vector<Neuron>());
		inputs = new InputNeuron[4];
		outputs = new Neuron[numOutputs];
		for (int i=0; i<inputs.length; i++){
			inputs[i] = new InputNeuron();
			inputNeurons.add(inputs[i]);
		}

		hiddenNeurons.add(makeHiddenLayer(N, inputNeurons, new SegmoidNeuron()));
		for (int j=1; j<layers; j++){
			Vector<Neuron> layer = makeHiddenLayer(N, hiddenNeurons.get(j-1), new SegmoidNeuron());
			hiddenNeurons.add(layer);
		}

		outputNeurons = makeHiddenLayer(numOutputs, hiddenNeurons.get(layers-1), new TresholdNeuron());
		for (int i=0; i<numOutputs; i++){
			outputs[i] = outputNeurons.get(i);
		}
	}

	private Vector<Neuron> makeHiddenLayer(int N, Vector<? extends Neuron> ins, Neuron factory){
		Vector<Neuron> ans = new Vector<Neuron>();
		for (int i=0; i<N; i++){
			Neuron n = factory.createInstance();
			ans.add(n);

			for (Neuron n1: ins){
				n.connectIn(n1, (Math.random()-0.5)*2.0);
				n1.connectOut(n);
			}
		}
		return ans;
	}



	private Data[][] readFile(String filename, boolean shuffle){
		Data[][] ans = null;
		try {
			BufferedReader in = new BufferedReader(new FileReader(filename));
			String line = in.readLine();
			ans = new Data[3][50];
			int index = 0;
			while (line != null && !line.isEmpty() && !line.equalsIgnoreCase("end")){
				Map<Neuron, Double> ins = new HashMap<Neuron, Double>();
				Map<Neuron, Double> outs = new HashMap<Neuron, Double>();
				String[] split = line.split(",");
				for (int i=0; i<4; i++){
					double num = Double.parseDouble(split[i]);
					ins.put(inputs[i], num);
				}
				String flowerName = split[4];
				for (int i=0; i<outputs.length; i++){
					outs.put(outputs[i], WRONGVAL);
				}
				if (flowerName.equalsIgnoreCase("Iris-setosa")){
					outs.put(outputs[0], RIGHTVAL);
					outs.put(outputs[1], RIGHTVAL);
				} else if (flowerName.equalsIgnoreCase("Iris-versicolor")){
					outs.put(outputs[0], RIGHTVAL);
				} else if (flowerName.equalsIgnoreCase("Iris-virginica")){
					outs.put(outputs[1], RIGHTVAL);
				}
				//System.out.println("line = " + line + " index = " + index);
				ans[index/50][index%50] = new Data(ins, outs);
				index++;
				line = in.readLine();
			}
			if (shuffle){
				//shuffle the inputs
				for (int i=0; i<ans.length; i++){
					Vector<Integer> tmp = new Vector<Integer>();
					for (int j=0; j<ans[i].length; j++){
						tmp.add(j);
					}
					Collections.shuffle(tmp);
					Data[] newData = new Data[ans[i].length];
					for (int j=0; j<newData.length; j++){
						newData[j] = ans[i][tmp.get(j)];
					}
					ans[i] = newData;
				}
			}

			if (index!=150) System.out.println("Wrong number of samples aquired - " + index );
		} catch (IOException e) {
			e.printStackTrace();
		}

		return ans;

	}

	public static double[] runBackProb(Data[][] data, NeuralNetwork net, double learningRate, double trainingSetPerc){
		BackPropagation bp = new BackPropagation(net);
		Vector<Integer> lottery = new Vector<Integer>();
		Vector<Integer> trainingSet = new Vector<Integer>();

		int dataLength = data[0].length;
		for (int j=0; j<dataLength; j++){
			lottery.add(j);
		}
		Collections.shuffle(lottery);

		for (int i=0; i<dataLength*trainingSetPerc; i++){
			trainingSet.add(lottery.remove(0));
		}

		for (int i=0; i<trainingSet.size(); i++){
			int num = trainingSet.get(i);
			Vector<Integer> lotto2 = new Vector<Integer>();
			for (int j=0; j<data.length; j++){
				lotto2.add(j);
			}
			Collections.shuffle(lotto2);
			for (int j=0; j<data.length; j++){
				int jIndex = lotto2.get(j);
				bp.addData(data[jIndex][num], learningRate);
				//System.out.println("added data "+jIndex+"- " + num);//+ "  " + data[j][jIndex]);
			}
		}


		int[] res = net.testData(lottery, data);
		//System.out.println("Test results - rest of data: " + res[0] + "/" + res[1]);

		double res1 = res[0]/(double)res[1];

		res = net.testData(trainingSet, data);

		//System.out.println("Test results - test data: " + res[0] + "/" + res[1]);
		double res2 = res[0]/(double)res[1];
		return new double[]{res1, res2};
	}

	public static void doNet(double trainingSetPerc) {
		int numTrys = 100;
		double learningRate = 0.1;
		try {
			for (int numLayers = 1; numLayers<4; numLayers++){
				PrintWriter out = new PrintWriter("iris-backprob-" + numLayers+ "-"+trainingSetPerc+".txt");
				out.println("numNeurons" + "\t" + "averageTest" + "\t" + "averageReal" + "\t" + "averageTotal" + "\t" + "bestTest" + "\t" + "bestReal" + "\t" + "bestTotal" + "\t" + "minTest"  + "\t" + "minReal" + "\t" + "minTotal");
				System.out.println("numNeurons" + "\t" + "averageTest" + "\t" + "averageReal" + "\t" + "averageTotal" + "\t" + "bestTest" + "\t" + "bestReal" + "\t" + "bestTotal" + "\t" + "minTest"  + "\t" + "minReal" + "\t" + "minTotal");
				for (int i=0; i<(4-numLayers)*5+10; i++){
					System.out.println("Layer " + numLayers + ", Neurons " + (i*5+5));
					IrisNetwork net = new IrisNetwork(numLayers, i*5+5);
					Data[][] data = net.readFile("bezdekiris.data", true);
					double[] max = new double[]{0, 0, 0};
					double[] min = new double[]{1, 1, 1};
					double[] succ = new double[]{0, 0, 0};
					for (int j=0; j<numTrys; j++){
						double[] res = runBackProb(data, net, learningRate, trainingSetPerc);
						succ[0] += res[0];
						succ[1] += res[1];
						succ[2] += res[0]*(1-trainingSetPerc)+res[1]*trainingSetPerc;
						max[0] = Math.max(max[0], res[0]);
						max[1] = Math.max(max[1], res[1]);
						max[2] = Math.max(max[2], res[0]*(1-trainingSetPerc) + res[1]*trainingSetPerc);
						min[0] = Math.min(min[0], res[0]);
						min[1] = Math.min(min[1], res[1]);
						min[2] = Math.min(min[2], res[0]*(1-trainingSetPerc) + res[1]*trainingSetPerc);
					}
					writeData(i, succ, max, min, numTrys, out);
				}
				out.close();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	public static void writeData(int i, double[] succ, double[] max, double[] min, int numTrys, PrintWriter out){
		int numNeurons = i*5+5;
		double averageTest = succ[1]/(double)numTrys;
		double averageReal = succ[0]/(double)numTrys;
		double averageTotal = succ[2]/(double)numTrys;
		double bestTest = max[1];
		double bestReal = max[0];
		double bestTotal = max[2];
		double minTest = min[1];
		double minReal = min[0];
		double minTotal = min[2];
		out.println(numNeurons + "\t" + averageTest + "\t" + averageReal + "\t" + averageTotal + "\t" + bestTest + "\t" + bestReal + "\t" + bestTotal + "\t" + minTest  + "\t" + minReal + "\t" + minTotal);
		System.out.println(numNeurons + "\t" + averageTest + "\t" + averageReal + "\t" + averageTotal + "\t" + bestTest + "\t" + bestReal + "\t" + bestTotal + "\t" + minTest  + "\t" + minReal + "\t" + minTotal);
	}


	public static void main(String[] args) {
		//		doNetCombined(0.5);
		//		doNetCombined(0.2);
		doNet(0.5);
		doNet(0.2);
	}

	//CODE FOR THE OR NETWORK (second experiment) - ignore this please
	//	public static void doNetCombined(double trainingSetPerc) {
	//		int numTrys = 100;
	//		double learningRate = 0.1;
	//		try {
	//			for (int numLayers = 1; numLayers<4; numLayers++){
	//				PrintWriter out = new PrintWriter("iris-backprob-" + numLayers+ "-"+trainingSetPerc+".txt");
	//				out.println("numNeurons" + "\t" + "averageTest" + "\t" + "averageReal" + "\t" + "averageTotal" + "\t" + "bestTest" + "\t" + "bestReal" + "\t" + "bestTotal" + "\t" + "minTest"  + "\t" + "minReal" + "\t" + "minTotal");
	//				System.out.println("numNeurons" + "\t" + "averageTest" + "\t" + "averageReal" + "\t" + "averageTotal" + "\t" + "bestTest" + "\t" + "bestReal" + "\t" + "bestTotal" + "\t" + "minTest"  + "\t" + "minReal" + "\t" + "minTotal");
	//				for (int i=0; i<(4-numLayers)*5+10; i++){
	//					System.out.println("Layer " + numLayers + ", Neurons " + (i*5+5));
	//					IrisNetwork[] net = new IrisNetwork[]{
	//							new IrisNetwork(numLayers, i*5+5),
	//							new IrisNetwork(numLayers, i*5+5),
	//							new IrisNetwork(numLayers, i*5+5)
	//					};
	//					Data[][][] data = new Data[][][]{
	//							net[0].readFile("bezdekiris.data", 0, false),
	//							net[1].readFile("bezdekiris.data", 1, false),
	//							net[2].readFile("bezdekiris.data", 2, false)
	//					};
	//					//shuffle the vectors
	//					for (int k=0; k<3; k++){
	//						Vector<Integer> tmp = new Vector<Integer>();
	//						for (int j=0; j<data[0][k].length; j++){
	//							tmp.add(j);
	//						}
	//						Collections.shuffle(tmp);
	//						for (int p=0; p<3; p++){
	//							Data[] newData = new Data[data[p][k].length];
	//							for (int j=0; j<newData.length; j++){
	//								newData[j] = data[p][k][tmp.get(j)];
	//							}
	//							data[p][k] = newData;
	//						}
	//					}
	//					double[] max = new double[]{0, 0, 0};
	//					double[] min = new double[]{1, 1, 1};
	//					double[] succ = new double[]{0, 0, 0};
	//					for (int j=0; j<numTrys; j++){
	//						double[] res = runBackProbCombined(data, net, learningRate, trainingSetPerc);
	//						succ[0] += res[0];
	//						succ[1] += res[1];
	//						succ[2] += res[0]*(1-trainingSetPerc)+res[1]*trainingSetPerc;
	//						max[0] = Math.max(max[0], res[0]);
	//						max[1] = Math.max(max[1], res[1]);
	//						max[2] = Math.max(max[2], res[0]*(1-trainingSetPerc) + res[1]*trainingSetPerc);
	//						min[0] = Math.min(min[0], res[0]);
	//						min[1] = Math.min(min[1], res[1]);
	//						min[2] = Math.min(min[2], res[0]*(1-trainingSetPerc) + res[1]*trainingSetPerc);
	//					}
	//					writeData(i, succ, max, min, numTrys, out);
	//				}
	//				out.close();
	//			}
	//		} catch (FileNotFoundException e) {
	//			e.printStackTrace();
	//		}
	//	}

	//	public static double[] runBackProbCombined(Data[][][] data, NeuralNetwork[] net, double learningRate, double trainingSetPerc){
	//		BackPropagation[] bp = new BackPropagation[]{
	//				new BackPropagation(net[0]),
	//				new BackPropagation(net[1]),
	//				new BackPropagation(net[2])
	//		};
	//		Vector<Integer> lottery = new Vector<Integer>();
	//		Vector<Integer> trainingSet = new Vector<Integer>();
	//
	//		int dataLength = data[0][0].length;
	//		for (int j=0; j<dataLength; j++){
	//			lottery.add(j);
	//		}
	//		Collections.shuffle(lottery);
	//
	//		for (int i=0; i<dataLength*trainingSetPerc; i++){
	//			trainingSet.add(lottery.remove(0));
	//		}
	//
	//		for (int i=0; i<trainingSet.size(); i++){
	//			int num = trainingSet.get(i);
	//			Vector<Integer> lotto2 = new Vector<Integer>();
	//			for (int j=0; j<data.length; j++){
	//				lotto2.add(j);
	//			}
	//			Collections.shuffle(lotto2);
	//			for (int j=0; j<data.length; j++){
	//				int jIndex = lotto2.get(j);
	//				bp[0].addData(data[0][jIndex][num], learningRate);
	//				bp[1].addData(data[1][jIndex][num], learningRate);
	//				bp[2].addData(data[2][jIndex][num], learningRate);
	//				//				System.out.println("added data "+jIndex+"- " + num);//+ "  " + data[j][jIndex]);
	//			}
	//		}
	//
	//
	//		int[] res = testDataCombined(lottery, data, net);
	//		//		System.out.println("Test results - rest of data: " + res[0] + "/" + res[1]);
	//
	//		double res1 = res[0]/(double)res[1];
	//
	//		res = testDataCombined(trainingSet, data, net);
	//		//		System.out.println("Test results - test data: " + res[0] + "/" + res[1]);
	//		double res2 = res[0]/(double)res[1];
	//
	//		return new double[]{res1, res2};
	//	}

	//	public static int[] testDataCombined(Vector<Integer> set, Data[][][] data, NeuralNetwork[] net){
	//		int total = 0;
	//		int succ = 0;
	//		for (int i=0; i<set.size(); i++){
	//			for (int j=0; j<data[0].length; j++){
	//				boolean ans = true;
	//				for (int k=0; k<data.length; k++){
	//					Data d = data[k][j][set.get(i)];
	//					ans = ans && net[k].testOut(d);
	//				}
	//				if (ans){
	//					succ++;
	//				}
	//				total ++ ;
	//			}
	//		}
	//		return new int[]{succ, total};
	//	}
}
